// *********************   lodash   *********************
// const _ = require('lodash');

// const arr = ['jack', 'tom','kate', 'lucy',  'jerry'];


// console.log(_.first(arr));
// console.log(_.last(arr));

// console.log(_.toUpper(_.first(arr)));

// console.log(_.reverse(arr));

// const arr2 = _.each(arr, (item, index) => {
//     console.log(item, index);
// });

// console.log(arr2);

// *********************   柯里化 - lodash   *********************

// const _ = require('lodash');
// function getSum(a, b, c) {
//     return a + b + c;
// }

// const curriedGetSum = _.curry(getSum);

// console.log(curriedGetSum(1,2,3));

// // 参数不够时，返回柯里化以后的函数
// console.log(curriedGetSum(1)(2, 3));
// console.log(curriedGetSum(1, 2)(3));


// const _ = require('lodash' );
// const match = _.curry(function (reg, str) {
//     return str.match(reg);
// });


// const haveSpace = match(/\s+/g);
// const haveNumber = match(/\d+/g);

// const filter = _.curry(function(func, arr) {
//     return arr.filter(func);
// });

// const filterSpace = filter(haveSpace);


// const arr = ['John Connor', 'John_Donne'];
// console.log(filter(haveSpace, arr));
// console.log(filterSpace(arr));


// *********************   柯里化   *********************

// function curry(func) {
//     return function curriedFn(...args) {
//         // 判断实参和形参的个数

//         if (args.length < func.length){
//             return function () {
//                 return curriedFn(...args.concat(Array.from(arguments)));
//             }
//         }

//         return func(...args);
//     }
// }

// function getSum(a, b, c) {
//     return a + b + c;
// }

// const curriedGetSum = curry(getSum);
// console.log(curriedGetSum(1,2,3));
// console.log(curriedGetSum(1)(2, 3));
// console.log(curriedGetSum(1,2)(3));


// *********************   柯里化 - 实现    *********************


// function curry (func) {
//     return function curriedFn(...args) {
//         if (args.length < func.length) {
//             // 参数不足时返回一个函数 
//             return function(...newArgs) {
//                 // 参数不足时返回的函数的入参 arguments， 旧入参 args
//                 // 此时重新需要判断 新旧参数加起来的个数是否和func形参的个数相等，形成递归
//                 // const allArgs = [...args, ...newArgs];
//                 // return curriedFn(...allArgs);
                
//                 return curriedFn(...[...args, ...newArgs]);
//             }
//         } else {
//             return func(...args);
//         }
        
//     }
// }

// *********************   函数组合    *********************


// function compose(f, g) {
//     return function(value) {
//         return f(g(value));
//     }
// }

// function reverse(array) {
//     return array.reverse();
// }

// function first(array) {
//     return array[0];
// }

// const last = compose(first, reverse);

// console.log(last([1,2,3,4]));


// const _ = require('lodash');

// const reverse = arr => arr.reverse();
// const first = arr => arr[0];
// const toUpper = s => s.toUpperCase();

// const f = _.flowRight(toUpper, first, reverse);
// console.log(f(['one', 'second', 'third']));


// const reverse = arr => arr.reverse();
// const first = arr => arr[0];
// const toUpper = s => s.toUpperCase();

// function compose(...args) {
//     return function(value) {
//         return args.reduceRight((prev, cur) => {
//             return cur(prev);
//         }, value);
//     }
// }

// const f = compose(toUpper, first, reverse);
// console.log(f(['one', 'second', 'third']));

// *********************   函数组合 - 结合律    *********************

// const _ = require('lodash');
// const f1 = _.flowRight(_.toUpper, _.first, _.reverse);
// const f2 = _.flowRight(_.flowRight(_.toUpper, _.first), _.reverse);
// const f3 = _.flowRight(_.toUpper, _.flowRight(_.first, _.reverse));

// const arr = ['first', 'second', 'third'];

// console.log(f1([...arr]), f2([...arr]), f3([...arr]));

// *********************   函数组合 - 调试   *********************

// 'NEVER SAY DIY' --> 'never-say-die'
// const _ = require('lodash');

// // const log = v => {
// //     console.log(v);
// //     return v;
// // }

// const trace = _.curry((tag, v) => {
//     console.log(tag, v);
//     return v;
// });

// // _.split()
// const split = _.curry((sep, str) => _.split(str, sep));

// // _.toLower()

// const join = _.curry((sep, str) => _.join(str, sep));

// const map = _.curry((fn, arr) => _.map(arr, fn));

// const f = _.flowRight(join('-'), trace('map 之后'), map(_.toLower), trace('map 之前'), split(' '));


// console.log(f('NEVER SAY DIY'))

// *********************   函数组合 - 柯里化   *********************

// 'NEVER SAY DIY' --> 'never-say-die'
// const _ = require('lodash');
// const fp = require('lodash/fp');

// // const log = v => {
// //     console.log(v);
// //     return v;
// // }

// const trace = _.curry((tag, v) => {
//     console.log(tag, v);
//     return v;
// });

// // _.split()  不使用被注释的 split 方法时为了减少硬编码 “空格”
// const split = _.curry((sep, str) => _.split(str, sep));
// // const split = str => str.split(' ');

// // _.toLower()

// const join = _.curry((sep, str) => _.join(str, sep));

// const map = _.curry((fn, arr) => _.map(arr, fn));

// // flowRight 接受的函数为(函数优先，数据之后)的函数，lodash提供的是(数据优先，函数之后)的函数
// // 可以直接使用 lodash/fp 模块提供的curry化以后的函数

// const f = _.flowRight(join('-'), trace('map 之后'), map(_.toLower), trace('map 之前'), split(' '));

// const f2 = fp.flowRight(fp.join('-'), fp.map(fp.toLower), fp.split(' '));

// console.log(f('NEVER SAY DIY'));
// console.log(f2('NEVER SAY DIY'));


// *********************   lodash map 问题   *********************

// const _ = require('lodash');
// const result  = _.map(['23', '8', '10'], parseInt);
// console.log('result', result); // [23, NaN, 2];


// const fp = require('lodash/fp');
// console.log(fp.map(parseInt, ['23', '8', '10']));

// *********************   point free   *********************


// const word = 'Hello World';

// function fn1(str) {
//     return str.toLowerCase().replace(/\s+/g, '_');
// }

// console.log(fn1(word));


// const fp = require('lodash/fp');
// const fn2 = fp.flowRight(fp.replace(/\s+/g, '_'), fp.toLower);

// console.log(fn2(word));



// *********************   point free 2   *********************
// const word2 = 'world wild web';

// const fp = require('lodash/fp');
// const fn3 = fp.flowRight(fp.join('.'), fp.map(fp.toUpper), fp.map(fp.first), fp.split(' '));

// console.log(fn3(word2));

// const fn4 = fp.flowRight(fp.join('.'), fp.map(fp.flowRight(fp.toUpper, fp.first)), fp.split(' '));

// console.log(fn4(word2));


// *********************   函子   *********************
// class Container {
//     constructor (value) {
//         this._value = value;
//     }
//     map(fn) {
//         return new Container(fn(this._value));
//     }
// }


// const r = new Container(5).map(x => x + 1).map(x => x * x);

// console.log(r);

// class Container {
//     static of (value) {
//         return new Container(value);
//     }
//     constructor (value) {
//         this._value = value;
//     }
//     map(fn) {
//         return Container.of(fn(this._value));
//     }
// }

// const r = Container.of(5).map(x => x + 1).map(x => x * x);

// console.log(r);

// *********************   MayBe 函子   *********************

// class MayBe {
//     static of(value) {
//         return new MayBe(value);
//     }

//     constructor(value) {
//         this._value = value;
//     }

//     map(fn) {
//         // return MayBe.of(fn(this._value));
//         return this.isNothing() ? MayBe.of(this._value) : MayBe.of(fn(this._value));
//     }
    
//     isNothing() {
//         return this._value === null || this._value === undefined;
//     }
// }

// const r = MayBe.of('hello world').map(x => x.toUpperCase());

// console.log('r', r);

// const r2 = MayBe.of('hello world')
//     .map(x => x.toUpperCase())
//     .map(x => null)
//     .map(x => x.split(' '));

// console.log('r2', r2);


// *********************   Either 函子   *********************

// class Left {
//     static of(value) {
//         return new Left(value);
//     } 

//     constructor(value) {
//         this._value = value;
//     }

//     map(fn) {
//         return this;
//     }
// }

// class Right {
//     static of(value) {
//         return new Right(value);
//     } 

//     constructor(value) {
//         this._value = value;
//     }

//     map(fn) {
//         return Right.of(fn(this._value));
//     }
// }


// const r1 = Right.of(12).map(x => x + 2);
// const r2 = Left.of(12).map(x => x + 2);

// console.log('r1', r1);
// console.log('r2', r2);

// const errJsonStr = '{a: b}';
// const curJsonStr = '{"a": "b"}';

// function parseJSON(str) {
//     try {
//         return Right.of(JSON.parse(str));
//     } catch (e) {
//         return Left.of(e);
//     }
// }

// const errR3 = parseJSON(errJsonStr);
// const curR4 = parseJSON(curJsonStr);

// console.log('errR3', errR3);
// console.log('curR4', curR4);

// const r5 = curR4.map(x => x.a.toUpperCase());

// console.log('r5', r5);

// *********************   IO 函子   *********************

// const fp = require('lodash/fp');

// class IO {
//     static of(value) {
//         return new IO(function() {
//             return value;
//         });
//     }

//     constructor(fn) {
//         this._value = fn;
//     }

//     map(fn) {
//         // 因为当前 _value 是一个函数，所以要把2个函数组合起来，而不是调用 IO.of
//         return new IO(fp.flowRight(fn, this._value));
//     }
// }

// // 调用
// const r = IO.of(process).map(p => p.execPath);
// console.log('r', r);
// const rVal = r._value();
// console.log('rVal', rVal);


// *********************   Folktale   *********************

// const { compose, curry } = require('folktale/core/lambda');
// const { toUpper, first } = require('lodash/fp');

// const f = curry(2, ((x, y) => x + y));

// console.log(f(1)(2));
// console.log(f(1, 2));


// const f2 = compose(toUpper, first);
// console.log(f2(['one', 'second']));

// *********************   Folktale 中 task函子   *********************

// const fs = require('fs');
// const path = require('path');
// const { task } = require('folktale/concurrency/task');
// const { split, find } = require('lodash/fp');

// function readFile(filename) {
//     return task(resolver => {
//         fs.readFile(filename, 'utf-8', (err, data) => {
//             if (err) {
//                 resolver.reject(err);
//             }
//             resolver.resolve(data);
//         });
//     });
// }

// readFile(path.resolve(__dirname, '../package.json'))
//     .map(split('\n'))
//     .map(find(x => x.includes('version')))
//     .run()
//     .listen({
//         onRejected: err => {
//             console.log('err', err);
//         },
//         onResolved: value => {
//             console.log('value', value);
//         }
//     });

// *********************   IO 函子的问题   *********************


// const fp = require('lodash/fp');
// const fs = require('fs');
// const path = require('path');

// class IO {
//     static of(value) {
//         return new IO(function() {
//             return value;
//         });
//     }

//     constructor(fn) {
//         this._value = fn;
//     }

//     map(fn) {
//         // 因为当前 _value 是一个函数，所以要把2个函数组合起来，而不是调用 IO.of
//         return new IO(fp.flowRight(fn, this._value));
//     }
// }

// const readFile = function(filename) {
//     return new IO(function () {
//         return fs.readFileSync(filename, 'utf-8');
//     });
// };

// const print = function(x) {
//     return new IO(function() {
//         console.log(x);
//         return x;
//     });
// };

// const cat = fp.flowRight(print, readFile);

// const result = cat(path.resolve(__dirname, '../package.json'));
// console.log('result', result);
// console.log('result._value() === ', result._value());
// console.log('result._value()._value() === ', result._value()._value());

// *********************   Monad 函子 -- 解决 IO 函子的问题   *********************

const fp = require('lodash/fp');
const fs = require('fs');
const path = require('path');

class IO {
    static of(value) {
        return new IO(function() {
            return value;
        });
    }

    constructor(fn) {
        this._value = fn;
    }

    map(fn) {
        // 因为当前 _value 是一个函数，所以要把2个函数组合起来，而不是调用 IO.of
        return new IO(fp.flowRight(fn, this._value));
    }

    join() {
        return this._value();
    }

    flatMap(fn) {
        return this.map(fn).join();
    }
}

const readFile = function(filename) {
    return new IO(function () {
        return fs.readFileSync(filename, 'utf-8');
    });
};

const print = function(x) {
    return new IO(function() {
        console.log(x);
        return x;
    });
};

const r = readFile(path.resolve(__dirname, '../package.json'))
    .map(x => x.toUpperCase())
    .flatMap(print)
    .join();

console.log('r', r);